#!/usr/bin/env python

#############################################################################
# clientlistener.py: Listener class for Client instances.
#############################################################################
# Copyright (C) 2012
#
# Authors: Dan Larkin <danielhlarkin@gmail.com>
#          Yonatan Naamad <cached@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#############################################################################

import logging
import threading, thread
import socket
import time
import torrent
from network import *

class ClientListener(threading.Thread):
    def __init__(self, client):
        self.client = client
        self._stop = threading.Event()
        
        self._outstanding_acks = []
        
        super(ClientListener, self).__init__()
        
    def stop(self):
        self._stop.set()
        
    def stopped(self):
        return self._stop.isSet()
        
    def run(self):
        '''Begin listening for requests from the server.'''
        logging.info("Beginning ClientListener thread.")
        
        while True:
            #receive data from the server
            data = ""
            while True:
                if self.stopped():
                    logging.debug("Client listener has been stopped.")
                    return
                try:
                    logging.info("Awaiting opcode...")
                    opcode = ord(self.client.socket.recv(1))
                    logging.info("Received opcode %d" % opcode)

                    if opcode == ALLOCATE_op:
                        d = read_packet(self.client.socket, ALLOCATE_op, ALLOCATE_schema)
                        self.handle_allocate(d)
                    else:
                        logging.warning("Unknown opcode '%d'" % opcode)
                except socket.timeout as e:
                    logging.warning("Socket timed out. Restarting.")
                    break
                except Exception as e:
                    logging.error("Quitting due to an unknown exception: %s" % e)
                    exit(0)
    
    def handle_allocate(self, d):
        '''Handles an allocation request.'''
        t_hash, min_alloc, max_alloc = d['T'], d['b_min'], d['b_max']
        
        #get the Torrent object
        t = torrent.get_instance(t_hash)
        logging.info("Allocate request for torrent %s to (min, max) = (%d, %d)" % (t, min_alloc, max_alloc))
        #check if the torrent is already running
        if t.is_active():
            #check if we received a STOP request (b_min == b_max == 0)
            if min_alloc == max_alloc == 0:
                #stop the torrent and mark as inactive
                logging.info("Allocate request sets min = max = 0. Removing torrent.")
                t.remove()
                return
            else:
                logging.info("Torrent %s is already active (%s)." % (t.get_hex(), t.is_active()))
                t.set_allocations(min_alloc, max_alloc)
        else:
            #we're going to want to add the torrent. acquire it, if necessary
            
            #note: acq_file is ASYNCHRONOUS, so we're telling it to add the torrent
            #and set allocations when done, since this function will be long completed
            
            def on_complete(t):
                t.add()
                t.set_allocations(min_alloc, max_alloc)
                
            def on_failure(t, err):
                logging.warning("Call to ALLOCATE not successfully handled. Error: %s" % err)
            
            logging.info("Need to acquire .torrent file.")
            t.acq_file(on_complete, on_failure)

        #finally, set the allocations
        t.set_allocations(min_alloc, max_alloc)
